#include <windows.h>

#define SETUPBOX
#ifdef SETUPBOX
#include <windowsx.h>
#endif

#include <string.h>
#include <stdio.h>
#include <tchar.h>
#include <d3d9.h>
#include "d3dbase.h"
#include "resource.h"

namespace D3DBase
{

HWND hWnd;
TCHAR szAppName[] = _T("Win32 App");
WNDCLASSEX wndClass;
BOOL bQuit = TRUE;
LPDIRECT3D9 d3d;
LPDIRECT3DDEVICE9 dev;
D3DDISPLAYMODE mode;
D3DDISPLAYMODE curmode;
void (*shutdownCallback)();
int chosenDevice, chosenMode;
bool windowed;

void ShutdownD3D();

int Width() {
	return mode.Width;
}

int Height() {
	return mode.Height;
}

float GetAspectRatio() {
	return (float)Height() / (float)Width();
}

LPDIRECT3DDEVICE9 GetDevice() {
	return dev;
}

LRESULT CALLBACK WndProc( HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam ) {
	HDC hdc;
	PAINTSTRUCT ps;

	switch (iMsg) {
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		EndPaint(hWnd, &ps);
		return 0;

	case WM_KEYDOWN:
		switch (LOWORD(wParam)) {
		case VK_ESCAPE:     //  Pressing esc quits
			DestroyWindow(hWnd);
			PostQuitMessage( 0 );
			break;
			/*
			case MY_KEYS:
			hypotheticalScene->sendMessage(KEYDOWN...);
			*/
		}
		break;

	case WM_DESTROY:
		if (shutdownCallback)
			shutdownCallback();
		ShutdownD3D();
		PostQuitMessage( 0 );
		break;
	}

	return DefWindowProc( hWnd, iMsg, wParam, lParam );
}

LRESULT CALLBACK SetupDlgProc(HWND hWndDlg, UINT Msg, WPARAM wParam, LPARAM lParam) {
	switch(Msg) {
	case WM_INITDIALOG:
		{
			HWND devices = GetDlgItem(hWndDlg, IDC_DEVICES);
			HWND modes = GetDlgItem(hWndDlg, IDC_MODES);

			// Windowed default?
			Button_SetCheck(GetDlgItem(hWndDlg, IDC_FULLSCREEN), FALSE);
			Button_SetCheck(GetDlgItem(hWndDlg, IDC_FSAA), FALSE);

			int numDevs = d3d->GetAdapterCount();
			D3DADAPTER_IDENTIFIER9 ident;
			for (int i = 0; i < numDevs; i++)
			{
				d3d->GetAdapterIdentifier(i, 0, &ident);
				ListBox_AddString(devices, ident.Description);
			}
			ListBox_SetCurSel(devices, 0);
			SendMessage(hWndDlg, WM_COMMAND, IDC_DEVICES | (LBN_SELCHANGE << 16), 0);
			
			return TRUE;
		}

	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDOK:
			{
				int sel = ListBox_GetCurSel(GetDlgItem(hWndDlg, IDC_MODES));
				if (sel != -1) {
					chosenDevice = ListBox_GetCurSel(GetDlgItem(hWndDlg, IDC_DEVICES));
					chosenMode = sel;
					windowed = !Button_GetCheck(GetDlgItem(hWndDlg, IDC_FULLSCREEN));
					d3d->EnumAdapterModes(chosenDevice, D3DFMT_X8R8G8B8, chosenMode, &mode);
					EndDialog(hWndDlg, IDOK);
				}
			}
			return TRUE;

		case IDCANCEL:
			EndDialog(hWndDlg, IDCANCEL);
			return TRUE;

		case IDC_DEVICES:
			if (HIWORD(wParam) == LBN_SELCHANGE) {
				HWND devices = GetDlgItem(hWndDlg, IDC_DEVICES);
				HWND modes = GetDlgItem(hWndDlg, IDC_MODES);
				ListBox_ResetContent(modes);
				int sel = ListBox_GetCurSel(devices);
				d3d->GetAdapterDisplayMode(sel, &curmode);
				int active = -1;
				if (sel != -1) {
					int numModes = d3d->GetAdapterModeCount(sel, D3DFMT_X8R8G8B8); 
					for (int i=0; i<numModes; i++) {
						d3d->EnumAdapterModes(sel, D3DFMT_X8R8G8B8, i, &mode);
						char temp[256];
						sprintf_s(temp, 256, "%ix%i @ %i Hz", mode.Width, mode.Height, mode.RefreshRate);
						ListBox_AddString(modes, temp);
						bool windowed = !Button_GetCheck(GetDlgItem(hWndDlg, IDC_FULLSCREEN));
						if (!windowed && mode.Width == curmode.Width && mode.Height == curmode.Height && mode.RefreshRate == curmode.RefreshRate)
							active = i;
						else if (windowed && mode.Width < curmode.Width && mode.Height < curmode.Height && mode.RefreshRate == curmode.RefreshRate)
							active = i;
					}
				}
				ListBox_SetCurSel(modes,active);
			}
			break;
		case IDC_MODES:
			if (HIWORD(wParam) == LBN_DBLCLK) {
				SendMessage(hWndDlg, WM_COMMAND, IDOK, 0);
			}
			break;
		}
		break;
	}
	return FALSE;
}

bool PreInitD3D() {
	if( NULL == ( d3d = Direct3DCreate9( D3D_SDK_VERSION ) ) )
        return false;
	else
		return true;
}

bool InitD3D(HWND hWnd) {
	D3DPRESENT_PARAMETERS d3dpp; 
	ZeroMemory( &d3dpp, sizeof(d3dpp) );

	d3dpp.BackBufferWidth = mode.Width;
	d3dpp.BackBufferHeight = mode.Height;
	
	d3dpp.Windowed = windowed ? TRUE : FALSE;
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
	d3dpp.EnableAutoDepthStencil = TRUE;
	d3dpp.hDeviceWindow = hWnd;
	d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
	d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
	
	if (!windowed) //necessary?
		d3dpp.FullScreen_RefreshRateInHz = mode.RefreshRate;

	d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;

	// Create the Direct3D device. Here we are using the default adapter (most
	// systems only have one, unless they have multiple graphics hardware cards
	// installed) and requesting the HAL (which is saying we want the hardware
	// device rather than a software one). Software vertex processing is 
	// specified since we know it will work on all cards. On cards that support 
	// hardware vertex processing, though, we would see a big performance gain 
	// by specifying hardware vertex processing.
	if (FAILED( d3d->CreateDevice( 
		chosenDevice, D3DDEVTYPE_HAL, hWnd, 
		D3DCREATE_HARDWARE_VERTEXPROCESSING,
		&d3dpp, &dev))) {
		return false;
	}

	D3DCAPS9 caps;
	dev->GetDeviceCaps(&caps);
	if (caps.PixelShaderVersion < 3) {
		dev->Release();
		return false;
	}
	return true;
}

//combined
void ShutdownD3D() {
	d3d->Release();
	if (dev)
		dev->Release();
}

HWND OpenWindow(HINSTANCE hInstance, void (*shutcallback)()) {
	shutdownCallback = shutcallback;
	//First, common D3D init
	if (!PreInitD3D()) {
		return 0;
	}
	if (IDCANCEL == DialogBox(hInstance, MAKEINTRESOURCE(IDD_SETUPDLG), 0, reinterpret_cast<DLGPROC>(SetupDlgProc))) {
		return 0;
	}

	wndClass.cbSize = sizeof(wndClass);
	wndClass.style  = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;
//	wndClass.cbClsExtra = 0;
//	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
//	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = szAppName;
	wndClass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

	RegisterClassEx(&wndClass);

	DWORD style = windowed ? WS_OVERLAPPEDWINDOW : WS_POPUP;

	RECT rc = {0, 0, mode.Width, mode.Height};
	AdjustWindowRect(&rc, style, false);
	
	int w = rc.right - rc.left;
	int h = rc.bottom - rc.top;
	// Center window
	rc.left = (curmode.Width - w) / 2;
	rc.right = rc.left + w;
	rc.top = (curmode.Height - h) / 2;
	rc.bottom = rc.top + h;

	hWnd = CreateWindow(szAppName, _T("Win32 App"),
		style,
		rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top,
		NULL, NULL, hInstance, NULL);

	ShowWindow(hWnd, SW_SHOW);
	BringWindowToTop(hWnd);

	bQuit = FALSE;
	if (!InitD3D(hWnd)) {
		MessageBox(0, "Failed to init D3D", 0, 0);
		shutdownCallback = 0;
		DestroyWindow(hWnd);
		return 0;
	}
	return hWnd;
}


bool Update() {
	MSG msg;

	while (PeekMessage( &msg, NULL, 0, 0, PM_REMOVE)) {
		TranslateMessage( &msg );
		DispatchMessage( &msg );
		if (msg.message == WM_QUIT)
			bQuit = TRUE;
	}

	return !bQuit;
}

}  // namespace